﻿using Bmz.MongoDb.Entities;
using MongoDB.Bson;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace Bmz.MongoDb.Repositories
{
    public class MongoRepository<T, TKey> : IRepository<T, TKey>
        where T : IEntity<TKey>
    {
        protected internal IMongoCollection<T> collection;

        public MongoRepository(string databaseName) : this("", databaseName) { }

        public MongoRepository(string connectionString, string databaseName)
        {
            this.collection = new MongoClient(connectionString).GetDatabase(databaseName).GetCollection<T>(typeof(T).Name);
        }

        public IMongoCollection<T> Collection
        {
            get { return this.collection; }
        }

        public virtual T GetById(TKey id)
        {
            var filter = Builders<T>.Filter.Eq(p => p.Id, id);
            return this.collection.Find(filter).FirstOrDefault();
        }

        public IQueryable<T> GetAll()
        {
            return this.collection.AsQueryable();
        }

        public virtual IEnumerable<T> GetList(FilterDefinition<T> filter)
        {
            return this.collection.Find(filter).ToEnumerable();
        }

        public virtual IEnumerable<T> GetList(Expression<Func<T, bool>> predicate)
        {
            return this.GetAll().Where(predicate).ToList();
        }

        public virtual ListResp<T> GetPage(int start, int rows, FilterDefinition<T> filter, SortDefinition<T> sort = null, bool hasTotal = false)
        {
            var result = new ListResp<T>();
            result.Start = start;
            result.Rows = rows;
            if (hasTotal)
            {
                result.Total = (int)this.collection.Find(filter).Count();
            }

            if (sort != null)
            {
                result.List = this.collection.Find(filter).Sort(sort).Skip(start).Limit(rows).ToEnumerable();
            }
            else
            {
                result.List = this.collection.Find(filter).Skip(start).Limit(rows).ToEnumerable();
            }
            return result;
        }

        public virtual ListResp<T> GetPage(int start, int rows, Expression<Func<T, bool>> predicate, Expression<Func<T, object>> sort = null, bool hasTotal = false)
        {
            var result = new ListResp<T>();
            result.Start = start;
            result.Rows = rows;
            if (hasTotal)
            {
                result.Total = (int)this.collection.AsQueryable().Where(predicate).Count();
            }

            if (sort != null)
            {
                result.List = this.collection.AsQueryable().OrderBy(sort).Where(predicate).Skip(start).Take(rows).ToList();
            }
            else
            {
                result.List = this.collection.AsQueryable().Where(predicate).Skip(start).Take(rows).ToList();
            }
            return result;
        }

        public virtual void Add(T entity)
        {
            this.collection.InsertOne(entity);
        }

        public virtual void Add(IEnumerable<T> entities)
        {
            this.collection.InsertMany(entities);
        }

        public virtual UpdateResult Update(FilterDefinition<T> filter, UpdateDefinition<T> update)
        {
            return this.collection.UpdateMany(filter, update);
        }

        public virtual UpdateResult Save(FilterDefinition<T> filter, UpdateDefinition<T> update)
        {
            return this.collection.UpdateMany(filter, update, new UpdateOptions() { IsUpsert = true });
        }

        public virtual DeleteResult Delete(TKey id)
        {
            var filter = Builders<T>.Filter.Eq(p => p.Id, id);
            return this.collection.DeleteOne(filter);
        }

        public virtual DeleteResult Delete(T entity)
        {
            return Delete(entity.Id);
        }

        public virtual DeleteResult Delete(FilterDefinition<T> filter)
        {
            return this.collection.DeleteMany(filter);
        }

        public virtual DeleteResult Delete(Expression<Func<T, bool>> predicate)
        {
            return this.collection.DeleteMany(predicate);
        }

        public virtual DeleteResult DeleteAll()
        {
            return this.collection.DeleteMany(p => true);
        }

        public virtual long Count()
        {
            return this.collection.Count(Builders<T>.Filter.Empty);
        }

        public virtual long Count(FilterDefinition<T> filter)
        {
            return this.collection.Count(filter);
        }

        public virtual long Count(Expression<Func<T, bool>> predicate)
        {
            return this.collection.Count(predicate);
        }

        public virtual bool Exists(Expression<Func<T, bool>> predicate)
        {
            return this.collection.Count(predicate) > 0;
        }
    }

    public class MongoRepository<T> : MongoRepository<T, ObjectId>, IRepository<T>
        where T : IEntity
    {
        public MongoRepository(string databaseName) : base(databaseName) { }

        public MongoRepository(string connectionString, string databaseName)
            : base(connectionString, databaseName)
        {
            this.collection = new MongoClient(connectionString).GetDatabase(databaseName).GetCollection<T>(typeof(T).Name);
        }
    }
}
